﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using System.IO;
using System.Xml;

using LuaInterface;

using SlimDX;
using SlimDX.Direct3D9;
using SlimDX.DirectInput;
using SlimDX.Windows;

using miciv.Rendering;
using miciv.Interfaces;
using miciv.Geometry;
using miciv.Misc;
using miciv.Job;

namespace miciv
{
    static class Program
    {
        class MICIVApplicaton : IApplicationLuaInterface
        {
            protected CoreContext m_coreContext;
            protected CoreManager m_coreManager;
            protected Form m_form;
            protected bool m_bQuit;
            protected Command m_command;
            protected object m_objLevel;
            protected string m_strLevelName;
            protected IUIManager m_uiManager;
            protected string m_strNickName;
            protected bool m_bResizing;

            public bool Create()
            {
                this.m_coreContext = new CoreContext();

                this.m_coreContext.AddData("JobManager", new JobManager(this.m_coreContext));

                this.m_form = new Form1();
                this.m_coreContext.AddData("Form", this.m_form);
                this.m_coreContext.AddData("ScreenSize", new Vector2(this.m_form.ClientSize.Width, this.m_form.ClientSize.Height));

                this.m_coreContext.AddData("ScreenSizeChanged", new ScreenSizeChangedHandler(this.ScreenSizeChanged));

                this.m_coreContext.AddData("Application", this as IApplicationLuaInterface);

                this.m_coreContext.AddData("ManagedResourceManager", new ManagedResourceManager());

                DataSourceManager dataSourceManager = new DataSourceManager(this.m_coreContext);
                this.m_coreContext.AddData("DataSourceManager", dataSourceManager);

                this.m_coreManager = new CoreManager(this.m_coreContext);
                this.m_coreContext.AddData("CoreManager", this.m_coreManager);
                this.m_coreContext.CoreManager = this.m_coreManager;

                PresentParameters ppConfig = new PresentParameters();
                ppConfig.BackBufferWidth = this.m_form.ClientSize.Width;
                ppConfig.BackBufferHeight = this.m_form.ClientSize.Height;
                SlimDX.Direct3D9.Device d3dDevice = new SlimDX.Direct3D9.Device(
                    new Direct3D(),
                    0,
                    SlimDX.Direct3D9.DeviceType.Hardware,
                    this.m_form.Handle,
                    CreateFlags.HardwareVertexProcessing,
                    ppConfig);
                this.m_coreContext.AddData("D3DDevice", d3dDevice);
                // all primitives must be drawn counter clockwise
                d3dDevice.SetRenderState(RenderState.CullMode, Cull.Clockwise);

                this.m_coreContext.AddData("VertexDefinitionManager", new VertexDefinitionManager(this.m_coreContext));
                this.m_coreContext.GetData<VertexDefinitionManager>("VertexDefinitionManager").LoadVertexDefinitions("Data/VertexDefinitions");

                this.m_coreContext.AddData("ShapeManager", new ShapeManager(this.m_coreContext));

                this.m_coreContext.AddData("RendererManager", new RendererManager(this.m_coreContext));

                LuaManager luaManager = new LuaManager();
                this.m_coreContext.AddData("LuaManager", luaManager);
                luaManager.Initialize(this.m_coreContext);
                luaManager.State["Application"] = this.m_coreContext.GetData<IApplicationLuaInterface>("Application");
                // Preload required file for SlimDX types, classes, etc support in all script files.
                luaManager.DoFile("Data/Scripting/Lua/SlimDXSupport.lua");

                this.m_coreManager.LoadManagersFromConfig("Data/modules.xml");
                this.m_coreManager.AddFolder("Data/UI");
                this.m_coreManager.AddFolder("Data/Levels/New");
                this.m_coreManager.AddFolder("Data/Levels/Save");

                SlimDX.DirectInput.DirectInput directInput = new SlimDX.DirectInput.DirectInput();
                this.m_coreContext.AddData("DirectInput", directInput);
                MouseInput mouseInput = new MouseInput();
                this.m_coreContext.AddData("MouseInput", mouseInput);
                mouseInput.Initialize(this.m_coreContext);

                this.m_coreContext.AddData("TextureManager", new TextureManager());
                this.m_coreContext.GetData<TextureManager>("TextureManager").Initialize(this.m_coreContext);

                this.m_coreContext.AddData("BitmapFontManager", new BitmapFontManager());

                StringTableManager stringTableManager = new StringTableManager(this.m_coreContext);
                this.m_coreContext.AddData("StringTableManager", stringTableManager);
                stringTableManager.SetDefaultLanguage(CultureInfo.CurrentCulture.Name, "en-EN");
                // load some string tables (before modules load since some module may need stringtable data)
                stringTableManager.LoadStringTable("Data/Localization/en-EN/uistringtable.xml");
                stringTableManager.LoadStringTable("Data/Localization/en-EN/unitstringtable.xml");
                stringTableManager.LoadStringTable(string.Format("Data/Localization/{0}/uistringtable.xml", CultureInfo.CurrentCulture.Name));
                stringTableManager.LoadStringTable(string.Format("Data/Localization/{0}/unitstringtable.xml", CultureInfo.CurrentCulture.Name));

                // new party list data source 
                DataSource newPartyList = new DataSource();
                newPartyList.Updater = DataSource.UpdateNewPartyList;
                // previous party list data source 
                DataSource loadPartyList = new DataSource();
                loadPartyList.Updater = DataSource.UpdateSavedPartyList;

                this.m_uiManager = this.m_coreManager.CreateUIManager("UIManager");
                this.m_coreContext.AddData("UIManager", this.m_uiManager);
                this.m_uiManager.Initialize(
                    this.m_coreContext,
                    (float)this.m_form.ClientSize.Width,
                    (float)this.m_form.ClientSize.Height);
                luaManager.State["UI"] = this.m_uiManager;

                // load entity prototypes
                this.m_coreManager.LoadEntityPrototypes("Data/Entities");

                // declare all required data sources before they get requested (seems obvious but hey....)
                dataSourceManager.Add("newpartylist", newPartyList);
                dataSourceManager.Add("loadpartylist", loadPartyList);
                // 
                this.m_uiManager.LoadScene("main.xml");
                this.m_uiManager.LoadScene("newparty.xml");
                this.m_uiManager.LoadScene("loadparty.xml");
                this.m_uiManager.ActivateScene("mainmenu", false, false);

                this.m_form.MouseMove += new MouseEventHandler(this.m_form_MouseMove);
                this.m_form.ResizeBegin += new EventHandler(this.m_form_ResizeBegin);
                this.m_form.ResizeEnd += new EventHandler(this.m_form_ResizeEnd);
                this.m_form.SizeChanged += new EventHandler(this.m_form_SizeChanged);

                // move form at current mouse position
                this.m_form.Left = System.Windows.Forms.Control.MousePosition.X;
                this.m_form.Top = System.Windows.Forms.Control.MousePosition.Y;

                this.m_bQuit = false;
                this.m_command = new Command();
                this.m_objLevel = null;
                this.m_strNickName = "UnnamedPlayer";
                this.m_bResizing = false;

                // load factions configuration AFTER entity prototypes
                {
                    object[] commandArgs = { "Data/Config/factions.xml" };
                    this.m_command.Name = "LoadFactionConfig";
                    this.m_command.Args = commandArgs;
                    this.m_coreManager.ExecuteCommand(this.m_command);
                }

                return true;
            }

            void m_form_ResizeBegin(object sender, EventArgs e)
            {
                // detect the begining of a window resize
                this.m_bResizing = true;
            }

            protected void m_form_SizeChanged(object sender, EventArgs e)
            {
                // window resizing : do nothing except if it's a one-shot resize (i.e. maximize <=> normal).
                if (false == this.m_bResizing)
                {
                    this.UpdateScreenSizeChange();
                }
            }

            protected void m_form_ResizeEnd(object sender, EventArgs e)
            {
                // end of window resizing : update and notify all related objects.
                this.m_bResizing = false;
                this.UpdateScreenSizeChange();
            }

            protected void UpdateScreenSizeChange()
            {
                try
                {
                    // release all d3d device created data before reset
                    this.m_coreContext.GetData<ManagedResourceManager>("ManagedResourceManager").DeviceLost();
                    // reset consists simply in changing the back buffer size to be the same as the window client area size
                    PresentParameters ppConfig = new PresentParameters();
                    ppConfig.BackBufferWidth = this.m_form.ClientSize.Width;
                    ppConfig.BackBufferHeight = this.m_form.ClientSize.Height;
                    SlimDX.Direct3D9.Device d3dDevice = this.m_coreContext.GetData<SlimDX.Direct3D9.Device>("D3DDevice");
                    // actual device reset
                    d3dDevice.Reset(ppConfig);
                    // set back desired render states
                    d3dDevice.SetRenderState(RenderState.CullMode, Cull.Clockwise);
                    // notify managed resources so they can allocate back new device objects
                    this.m_coreContext.GetData<ManagedResourceManager>("ManagedResourceManager").DeviceReset();

                    // oops, ui manager need to known that screen size has changed :p
                    this.m_uiManager.Width = this.m_form.ClientSize.Width;
                    this.m_uiManager.Height = this.m_form.ClientSize.Height;
                    // 
                    this.m_coreContext.ReplaceData<Vector2>("ScreenSize", new Vector2(this.m_form.ClientSize.Width, this.m_form.ClientSize.Height));
                    ScreenSizeChangedHandler handlerScreenSizeChanged = this.m_coreContext.GetData<ScreenSizeChangedHandler>("ScreenSizeChanged");
                    if (null != handlerScreenSizeChanged)
                    {
                        handlerScreenSizeChanged(this.m_form.ClientSize.Width, this.m_form.ClientSize.Height);
                    }
                }
                catch (Direct3D9Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                }
            }

            protected void ScreenSizeChanged(int _sWidth, int _sHeight)
            {
                this.m_coreContext.GetData<RendererManager>("RendererManager").ScreenSizeChanged(_sWidth, _sHeight);
            }

            protected void m_form_MouseMove(object sender, MouseEventArgs e)
            {
                MouseInput mi = this.m_coreContext.GetData<MouseInput>("MouseInput");
                mi.X = e.X;
                mi.Y = e.Y;
            }

            public void Run()
            {
                ApplicationContext ac = new ApplicationContext(this.m_form);
                MessagePump.Run(ac, this.UpdateFrame);
            }

            protected void UpdateFrame()
            {
                float deltaTime = 0.0f;

                this.m_coreContext.GetData<MouseInput>("MouseInput").Update();
                this.m_uiManager.Update(deltaTime);

                //if (false != this.m_coreContext.GetData<IApplicationLuaInterface>("Application").Quit)
                if (false != this.Quit)
                {
                    this.m_coreContext.GetData<Form>("Form").Close();
                }
                else
                {
                    if (null != this.m_objLevel)
                    {
                        object[] commandArgs = { this.m_objLevel, deltaTime };
                        this.m_command.Name = "UpdateLevel";
                        this.m_command.Args = commandArgs;
                        this.m_coreManager.ExecuteCommand(this.m_command);
                    }

                    RenderFrame();

                    this.m_coreContext.GetData<DataSourceManager>("DataSourceManager").ClearHasChanged();
                }
            }

            protected void RenderFrame()
            {
                SlimDX.Direct3D9.Device d3dDevice = this.m_coreContext.GetData<SlimDX.Direct3D9.Device>("D3DDevice");
                // only clear color buffer.
                // use IRendererContext.ClearOptions to setup a specific clear.
                d3dDevice.Clear(ClearFlags.Target, Color.DarkSlateGray, 0.0f, 0);
                d3dDevice.BeginScene();

                this.m_coreContext.GetData<RendererManager>("RendererManager").Render();

                d3dDevice.EndScene();
                d3dDevice.Present();
            }

            public void Release()
            {
                if (null != this.m_objLevel)
                {
                    object[] commandArgs = { this.m_strLevelName };
                    this.m_command.Name = "UnloadLevel";
                    this.m_command.Args = commandArgs;
                    this.m_coreManager.ExecuteCommand(this.m_command);
                    this.m_objLevel = null;
                    this.m_coreContext.GetData<LuaManager>("LuaManager").State["Level"] = null;
                }

                this.m_uiManager.UnloadScenes();
                this.m_coreManager.ReleaseUIManager(this.m_uiManager);

                foreach (ComObject item in ObjectTable.Objects)
                {
                    item.Dispose();
                }

                this.m_coreManager.UnloadManagers();
            }

            public void Test(string _strText)
            {
                System.Diagnostics.Debug.WriteLine(_strText);
            }

            #region IApplicationLuaInterface Membres

            public bool Quit
            {
                get
                {
                    return this.m_bQuit;
                }
                set
                {
                    this.m_bQuit = value;
                }
            }

            public bool PreloadLevel(string _strLocation)
            {
                // retrieve players and factions info
                this.m_strLevelName = this.m_coreManager.GetExistingFile(_strLocation + ".xml");
                object[] commandArgs = { this.m_strLevelName, this.m_strNickName };
                this.m_command.Name = "LoadSettings";
                this.m_command.Args = commandArgs;
                bool bResult = this.m_coreManager.ExecuteCommand(this.m_command);
                if (false != bResult)
                {
                }
                return bResult;
            }

            public bool LoadLevel(string _strLocation)
            {
                bool bResult = true;
                // first : unload current level
                if (null != this.m_objLevel)
                {
                    object[] commandArgs = { this.m_strLevelName };
                    this.m_command.Name = "UnloadLevel";
                    this.m_command.Args = commandArgs;
                    bResult = this.m_coreManager.ExecuteCommand(this.m_command);
                }
                // then : load requested level
                if (false != bResult)
                {
                    this.m_strLevelName = this.m_coreManager.GetExistingFile(_strLocation + ".xml");
                    object[] commandArgs = { this.m_strLevelName, this.m_strNickName };
                    this.m_command.Name = "LoadLevel";
                    this.m_command.Args = commandArgs;
                    bResult = this.m_coreManager.ExecuteCommand(this.m_command);
                    if (false != bResult)
                    {
                        // load succeeded : store returned level object for future use
                        this.m_coreContext.GetData<LuaManager>("LuaManager").State["Level"] = this.m_command.Result;
                        this.m_objLevel = this.m_command.Result;
                        this.m_uiManager.DeactivateScene(false, false);
                        this.m_uiManager.ClearScenesStack();
                    }
                    else
                    {
                        this.m_coreContext.GetData<LuaManager>("LuaManager").State["Level"] = null;
                        this.m_strLevelName = string.Empty;
                        this.m_objLevel = null;
                    }
                }
                return bResult;
            }

            public bool LoadLevelFromCurrentSettings()
            {
                bool bResult = true;
                // first : unload current level
                if (null != this.m_objLevel)
                {
                    object[] commandArgs = { this.m_strLevelName };
                    this.m_command.Name = "UnloadLevel";
                    this.m_command.Args = commandArgs;
                    bResult = this.m_coreManager.ExecuteCommand(this.m_command);
                }
                // then : load requested level
                if (false != bResult)
                {
                    object[] commandArgs = { };
                    this.m_command.Name = "LoadLevelFromCurrentSettings";
                    this.m_command.Args = commandArgs;
                    bResult = this.m_coreManager.ExecuteCommand(this.m_command);
                    if (false != bResult)
                    {
                        // load succeeded : store returned level object for future use
                        this.m_coreContext.GetData<LuaManager>("LuaManager").State["Level"] = this.m_command.Result;
                        this.m_objLevel = this.m_command.Result;
                        this.m_uiManager.DeactivateScene(false, false);
                        this.m_uiManager.ClearScenesStack();
                    }
                    else
                    {
                        this.m_coreContext.GetData<LuaManager>("LuaManager").State["Level"] = null;
                        this.m_strLevelName = string.Empty;
                        this.m_objLevel = null;
                    }
                }
                return bResult;
            }

            #endregion
        };

        /// <summary>
        /// Point d'entrée principal de l'application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            MICIVApplicaton micivContext = new MICIVApplicaton();

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            micivContext.Create();
            micivContext.Run();
            micivContext.Release();
        }
    }
}
